/**
 * Advanced Software Engineering (Monica Farrow)
 * CustomerController class
 * 
 * Some of the methods in this class have been based on lecture notes provided by
 * Monica Farrow as a part of Software Engineering courses at Heriot-Watt University.
 */

package depotControllers;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Scanner;

import javax.swing.JOptionPane;

import depot.*;
import depotAdmin.*;
import depotViews.CustomerGUI;
import depotModels.*;

/**
 * CustomerController class - mainly controls the CustomerQueue model
 * and also interacts with the ParcelUncollected model
 * @author Ionut Iordache, Nikky Afunugo, Dave Bond
 */
public class CustomerController implements Runnable{

	private CustomerGUI view; // allows controller to respond to ActionEvents in GUI
	private CustomerQueue model;   // stores customer queue
	private ParcelUncollected model2; // stores uncollected parcels
	private String eventLogEntry = "";  // stores log entries

	/**
	 * constructor for CustomerController
	 * registers models and view
	 * @param the CustomerGUI object (view)
	 * @param the CustomerQueue object (model)
	 * @param the ParcelUncollected object (model)
	 */
	public CustomerController(CustomerGUI view, CustomerQueue model, ParcelUncollected model2) {
		this.view = view;
		this.model = model;
		this.model2 = model2;		

	}

	/**
	 * run method - does all the work
	 * oversees the processing of the CustomerQueue
	 */
	public void run() {
		// initialise CustomerQueue with waiting customers
		processInputFile("custs.csv");

		//add some randomly generated customers throughout working day
		addRandomCusts();

		// tidy up at day's end
		tidyUp();

		// produce 'parcel' part of final report
		model2.compileReport();

		// short delay to ensure all processing finished
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		} 

		// display final message and shut down
		String finalMessage = "Depot admin closed down.\r\n\r\n";
		finalMessage += "Final report (report.txt) and event log (eventLog.txt)";
		finalMessage += " are avalaible for your perusal.\r\n\r\nAu revoir.";
		JOptionPane.showMessageDialog(null, finalMessage);	
		System.exit(0);
	}

	/** Takes filename as parameter
	 * reads file line-by-line
	 * passes each line to separate method for processing
	 * Blank lines are skipped
	 * @param filename the name of the input file
	 */
	public void processInputFile(String filename) {
		int lineNum = 0;

		// read file into inputstream (so can be included in jar)
		InputStream is = getClass().getResourceAsStream(filename);

		Scanner scanner = null;
		scanner = new Scanner(is);

		while (scanner.hasNextLine()) {
			try {
				lineNum++;

				// read first line and process it
				String inputLine = scanner.nextLine(); 

				if (inputLine.length() != 0) { // ignore blank lines
					String details [] = inputLine.split(",");

					// get parcel to embed in customer object
					Parcel pcl = model2.getParcelByID(details[3], "getCustPcl");
					if (pcl == null) {
						throw new IllegalArgumentException("ParcelID "+details[3]+" does not exist");
					}

					// process line
					model.processLine(pcl,inputLine,lineNum);

					// remove parcel from model2 (ParcelUncollected), after customer processed
					model2.removeParcel(details[3]);
				}
			} 

			// this catches invalid conversion from String to an integer or double
			// and produces an entry for the inputErrorLog file
			catch (NumberFormatException nfe) {
				eventLogEntry += "<ERR> custs.csv, ";
				eventLogEntry += "line "+lineNum+": illegal integer or double conversion.\r\n";
				model.writeToLog("eventLog.txt",eventLogEntry,"append");
				eventLogEntry = "";
			}

			// this catches missing items and produces an entry for the inputErrorLog file
			catch (ArrayIndexOutOfBoundsException nfe) {
				eventLogEntry += "<ERR> custs.csv, ";
				eventLogEntry += "line "+lineNum+": not enough elements.\r\n";
				model.writeToLog("eventLog.txt",eventLogEntry,"append");
				eventLogEntry = "";
			}

			// this catches (mainly) invalid format errors passed from constructors
			// and produces an entry for the inputErrorLog file
			catch (IllegalArgumentException iae) {
				eventLogEntry += "<ERR> custs.csv, ";
				eventLogEntry += "line "+lineNum+": "+iae.getMessage()+".\r\n";
				model.writeToLog("eventLog.txt",eventLogEntry,"append");
				eventLogEntry = "";
			}

			// this catches duplicate IDs and produces an entry for the inputErrorLog file
			catch (DuplicateIDException die) {
				eventLogEntry += "<ERR> custs.csv, ";
				eventLogEntry += "line "+lineNum+": "+die.getMessage()+".\r\n";
				model.writeToLog("eventLog.txt",eventLogEntry,"append");
				eventLogEntry = "";
			}

		}

		scanner.close();
	}

	/**
	 * adds a number of randomly generated customers
	 * at regular intervals
	 */
	public void addRandomCusts() {

		while (! DepotUtils.getEndOfDay()) {

			try { // delay to make visible in GUI

				// implement random delay based on current speed value
				long baseDelay = DepotUtils.getCustAddSpeed();
				// generate random modifier between 0.5 & 1.5
				Double randModifier = DepotUtils.getRandomDouble(0.5, 1.5);
				// apply modifier to base value and convert back to long
				Double convertedBase = baseDelay * randModifier;
				long thisDelay = convertedBase.longValue();

				Thread.sleep(thisDelay);

			} catch (InterruptedException e) {
			} 

			// randomly select parcel from ParcelUncollected
			Parcel pcl = model2.getRandomPcl();

			try {
				// randomly generate customer, using parcel
				Customer cust = DepotUtils.getRandCust(pcl);

				// checks custQueue for duplicate ID
				Customer cust99 = model.getCustomerByID(cust.getCustID());
				if(cust99 != null) {
					throw new DuplicateIDException("CustomerID " + cust.getCustID() + " already exists in customer queue");
				}

				// add customer to CustomerQueue
				model.addToQueue(cust);

			}

			// this catches (mainly) invalid format errors passed from constructors
			// and produces an entry for the inputErrorLog file
			catch (IllegalArgumentException iae) {
				eventLogEntry += "<ERR> Random customer: " + iae.getMessage() + "\r\n";
				model.writeToLog("eventLog.txt",eventLogEntry,"append");
				eventLogEntry = "";
			}

			// this catches duplicate IDs and produces an entry for the inputErrorLog file
			catch (DuplicateIDException die) {
				eventLogEntry += "<ERR> Random customer: " + die.getMessage() + "\r\n";
				model.writeToLog("eventLog.txt",eventLogEntry,"append");
				eventLogEntry = "";
			}

		}

	}

	/**
	 * tidies up at day's end by:
	 * checking processing thread finished
	 * checking parcel thread finished
	 * getting customers from customerQueue
	 * putting their parcels back into parcelList
	 * removing them from the queue
	 */
	public synchronized void tidyUp() {
		ArrayList<Customer> leftInQueue = null;

		// checks AutoGenrator.procLoopFinished=true
		model.setCustFinishedFlagPt1();

		// checks AutoGenrator.pclLoopFinished=true
		model2.setCustFinishedFlagPt2();

		// removes customers left in CustQueue & puts parcels back in parcelList
		leftInQueue = model.getCustsLeftInQueue();
		for (Customer cust : leftInQueue) {
			try { // delay to make visible in GUI
				Thread.sleep(DepotUtils.getInitAndTidyUpSpeed());
			} catch (InterruptedException e) {
			}

			// puts parcels back into parcelList
			model2.addParcelToCollection(cust.getParcelID(), cust.getParcel(), DepotUtils.getPclLoopFinished());

			// removes customers left in CustQueue
			model.removeFromQueue(cust, DepotUtils.getProcLoopFinished());

		}

		// set AutoGenrator.custLoopFinished=true
		DepotUtils.setCustLoopFinished(true);

		// notify any waiting threads
		notifyAll();
	} 

}
